process.env.TZ = 'Asia/Shanghai';

fs = require('fs');
// mysql = require('mysql');

https = require('https');

var uim = require('./uim.js');

constains = require('constants');

var exec_request = function (req, res) {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end('{"status":"ERROR"}');
};

// 载入环境配置（以下划线开头的配置表示允许在运行中重新赋值并立即生效）
ENV = require('./env.config.js').config;

console.log(ENV);

var default_socket_server_obj = {
    // id: 0,
    // host: 'socketserver-001.yourdomain.cn:5201', // wss/https地址
    clients: 0, // 当前在线客户数
    linking: 0, // 截止clients变量更新前，已经转发的客户数
    last_active_time: 0, // 最后活动时间（毫秒时间戳）
    last_report_time: 0, // 最后一次上报时间（毫秒时间戳）
    enable: false, // 是否可用
}
// 检查辖下服务器状态变化
var report_socket_server_state = function () {
    var now_time = new Date().getTime();
    for (var tmp_i in socket_server_list) {
        if (socket_server_list[tmp_i].last_active_time > socket_server_list[tmp_i].last_report_time
            && now_time - socket_server_list[tmp_i].last_active_time > ENV._ACTIVE_TIME_LIMIT
        ) {
            socket_server_list[tmp_i].enable = false;
            socket_server_list[tmp_i].last_report_time = now_time;
            // todo 向控制台发出socket-server掉线信息
            console.log('socket-server掉线：');
            console.log(socket_server_list[tmp_i]);
        }
    }
    setTimeout(report_socket_server_state, ENV._ACTIVE_TIME_LIMIT);
}
// 该路由辖下的服务器列表
var socket_server_list = {
    // '1': {}
};

// 该路由所服务的应用列表 
var app_list = {
    // '1': {
    //     app_id: 0,
    //     app_name: '',
    //     enable: true, // 应用启用状态
    // }
};


var init_app_list = function () {
    console_redis_pub.get('console_deploy|app_list|socket_router_' + ENV.SOCKET_ROUTER_ID, function (err, result) {
        if (err) {
            return console.log('读取应用列表失败');
        }
        var tmp_app_list = uim.parseJSON(result);
        if (!tmp_app_list) {
            return console.log('解析应用列表时数据异常');
        }
        for (var tmp_i in tmp_app_list) {
            app_list[tmp_app_list[tmp_i].app_id] = tmp_app_list[tmp_i];
        }
        console.log('初始应用列表成功：');
        console.log(app_list);
        init_socket_server_list(); //初始辖下socket-server列表
    });
}

var init_socket_server_list = function () {
    // 从redis/mysql中读取列表
    // 遍历并初始redis列表
    console_redis_pub.get('console_deploy|socket_server_list|socket_router_' + ENV.SOCKET_ROUTER_ID, function (err, result) {
        if (err) {
            throw '读取socket_server列表失败';
        }
        var tmp_socket_server_list = uim.parseJSON(result || '[]');
        if (!tmp_socket_server_list) {
            throw '解析socket_server列表时数据异常';
        }
        for (var tmp_i in tmp_socket_server_list) {
            socket_server_list[tmp_socket_server_list[tmp_i].socket_server_id] = Object.assign(tmp_socket_server_list[tmp_i], default_socket_server_obj);
        }
        console.log('初始socket-server列表成功：');
        console.log(socket_server_list);
        init_redis_subscribe();
    });
}

var redis = require('redis');
const { callbackify } = require('util');

// 用于连接router本机redis，用于与socket-server保持通信，接收update-server
var console_redis_sub = undefined;

// 用于连接router本机redis，用于与socket-server保持通信，向update-server发出广播事件
var console_redis_pub = redis.createClient(ENV.REDIS.PORT, ENV.REDIS.HOST, { auth_pass: ENV.REDIS.PASSWORD });
console_redis_pub.on('ready', function (err) {
    // 如果连接控制台失败
    if (err) {
        throw 'pub连接本地控制台失败'
    } else {
        console.log('pub已连接到本地控制台');
        init_app_list(); // 初始应用列表
    }
});
var init_redis_subscribe = function () {
    console_redis_sub = redis.createClient(ENV.REDIS.PORT, ENV.REDIS.HOST, { auth_pass: ENV.REDIS.PASSWORD });
    console_redis_sub.on('ready', function (err) {
        // 如果连接控制台失败
        if (err) {
            throw 'sub连接本地控制台失败'
        } else {
            console.log('sub已连接到本地控制台');
        }
    });
    console_redis_sub.subscribe('update_socket_server'); // 订阅本router下属server的状态更新信息
    console_redis_sub.subscribe('emit_event'); // 如果是由router连接外部，则按router编号订阅广播事件，如果是从外部连接router，则只从本机订阅
    console_redis_sub.on('subscribe', function (channel, count) {
        console.log(`got subscribe event: ${channel} and count is ${count}`);
    });
    console_redis_sub.on('connect', function () {
        //订阅客户端接收到消息
        console_redis_sub.on('message', function (channel, message) {
            var time = new Date().getTime();
            var response = `${time} received message from ${channel}:${message}`;
            // console.log(response);
            switch (channel) {
                case 'update_socket_server':
                    update_socket_server(message, time);
                    break;
                case 'emit_event':
                    emit_event(message, time);
                case 'debug':
                    run_debug(message);
                default:
                    break;
            }
        });
        ENV._STATE = 1; // socket-router已就绪
        console.log('socket-router已就绪');
        report_socket_server_state();
    });
}

var update_socket_server = function (message, time) {
    var tmp_arr = message.split(',');
    if (tmp_arr.length != 2 || !socket_server_list[tmp_arr[0]]) {
        return;
    }
    socket_server_list[tmp_arr[0]].clients = tmp_arr[1] * 1;
    socket_server_list[tmp_arr[0]].linking = 0;
    socket_server_list[tmp_arr[0]].last_active_time = time;
    if (false === socket_server_list[tmp_arr[0]].enable) {
        socket_server_list[tmp_arr[0]].enable = true;
        // todo 向控制台发出socket-server上线信息
        console.log('socket-server上线：');
        console.log(socket_server_list[tmp_arr[0]]);
    }
}
var emit_event = function (message, time) {
    var tmp_emit_event = uim.parseJSON(message);
    // ['socket_server_id', 'account_id', 'message', 'need_back']
    var tmp_socket_server_id = tmp_emit_event.shift();
    if (!tmp_socket_server_id || !tmp_emit_event[0]) {
        return;
    }
    if (socket_server_list[tmp_socket_server_id].enable) {
        console_redis_pub.publish('socket_server_' + tmp_socket_server_id, tmp_emit_event, function (err, reply) {
            if (tmp_emit_event[2] && err) {
                // todo back and retry
            }
        });

    }
}

// 负载均衡可直接在router域名解析上做，每个router只负责一个机房内的路由

var get_enable_socket_server = function () {
    var tmp_i, tmp_socket_server_id, tmp_clients = ENV._ONLINE_CLIENT_MAX;
    var tmp_all;
    // 从服务器列表找出当前在线数最少的机器
    for (tmp_i in socket_server_list) {
        if (socket_server_list[tmp_i].enable) {
            tmp_all = socket_server_list[tmp_i].clients + socket_server_list[tmp_i].linking;
            if (tmp_all < tmp_clients) {
                tmp_socket_server_id = tmp_i;
                tmp_clients = tmp_all;
            }
        }
    }
    if (tmp_clients > ENV._ONLINE_CLIENT_WARN) send_console_message({
        type: 'warn',
        title: '最少在线人数的服务器所维护的连接数超过预警值',
        content: { client_number: ENV._ONLINE_CLIENT_WARN, socket_server_list }
    });
    if (tmp_socket_server_id) {
        socket_server_list[tmp_socket_server_id].linking++;
    }
    return tmp_socket_server_id;
};
var send_console_message = function (obj) {
    obj.time = obj.time || new Date().getTime();
    var message = JSON.stringify(obj);
    console_redis_pub.rpush('console', message, function (err, reply) {
        if (err) {
            return console.log('发送控制台消息失败！')
        }
        console_redis_pub.publish('console', message);
    });
}



var new_client_list = {
    // datetime: { 'ip': [] },
    // '2020-08-23 07': { '::ffff:127.0.0.1': ['100000001'] },

    // '2020':{
    //     _total:0,
    //     'ip':{
    //         '::ffff:127.0.0.1':0
    //     },
    //     '08':{
    //         _total:0,
    //     }
    // }
};
// 生成新的客户端编号（数值类型，含router_id）
var get_new_client_id = function (ip, callback) {
    // 322626,67623978

    let datetime = uim.dateTime().substr(0, 13);
    // todo 每个ip每小时只允许产生100个，后期采用redis统计，可分析每天有多少新ip，每个ip产生多少编号，分布在一天内的哪些时段
    if (new_client_list[datetime] && new_client_list[datetime][ip] && new_client_list[datetime][ip].length > 100) {
        return callback('当前客户端生成新标识操作过于频繁！');
    }
    // 如果还没有当前日期的对象，则创建
    if (!new_client_list[datetime]) {
        // todo 清理过早的时间对象，为方便调试，暂先不清理，后期写入mysql
        new_client_list[datetime] = {};
    }

    if (ENV._NEW_CLIENT_ID_CREATED % ENV._NEW_CLIENT_ID_SAVE == 0) {
        // todo 每次生成写文件可优化成一段时间写一次（程序异常时会存在浪费。正常退出时可更新写）
        fs.writeFileSync('./dat/new_client_id', ENV._NEW_CLIENT_ID + ENV._NEW_CLIENT_ID_SAVE);
    }
    ENV._NEW_CLIENT_ID_CREATED++;
    ENV._NEW_CLIENT_ID++;
    var new_client_id = ENV._NEW_CLIENT_ID.toString() + ('0000000' + ENV.SOCKET_ROUTER_ID).substr(-8);

    // 如果在这个时间里是该IP第一次初始客户端，则创建对象
    if (!new_client_list[datetime][ip]) {
        new_client_list[datetime][ip] = [new_client_id];
    } else {
        new_client_list[datetime][ip].push(new_client_id);
    }
    // 将router_id拼接作为后8位，用于标识该client_id最初由哪个router产生
    callback(null, new_client_id);
}

var new_session_list = {
    // date: { 'client_id': [] },
    // '2020-08-23': { '100000001': ['100000001'] },
};

// 生成新的会话编号（数值类型，含router_id）
var get_new_session_id = function (client_id, ip, callback) {
    // todo 单个client_id一天内只允许创建最多100个会话
    let date = uim.dateTime().substr(0, 10);
    if (new_session_list[date] && new_session_list[date][client_id] && new_session_list[date][client_id].length > 100) {
        return callback('当前客户端生成新会话操作过于频繁！');
    }
    // 如果还没有当前日期的对象，则创建
    if (!new_session_list[date]) {
        // new_session_list={}; // 清理之前的日期对象，为方便调试，暂先不清理
        new_session_list[date] = {};
    }
    ENV._NEW_SESSION_ID_CREATED++;
    ENV._NEW_SESSION_ID++;
    // var time = new Date().getTime();
    // var new_session = {
    //     // 使用时间戳为主，将router_id拼接作为后8位，用于标识该session_id由哪个router产生，利于后期做会话漫游
    //     session_id: ENV._NEW_SESSION_ID + ('0000000' + ENV.SOCKET_ROUTER_ID).substr(-8),
    //     time,
    //     client_id,
    //     ip,
    //     // session_data: {},
    // };
    var new_session_id = ENV._NEW_SESSION_ID;//;
    var log_session_info = {
        session_id: new_session_id,
        ip,
    }
    // 如果当天是该客户端第一次创建会话，则创建对象
    if (!new_session_list[date][client_id]) {
        new_session_list[date][client_id] = [log_session_info];
    } else {
        new_session_list[date][client_id].push(log_session_info);
    }

    // 写入redis
    var new_session_json = JSON.stringify({
        // session_id: new_session_id,
        client_id,
        // time,
        ip,
        session_data: {},
    });
    let session_sn = ENV.SESSION_PREFIX + ENV.SOCKET_ROUTER_ID + '/' + client_id + '/' + new_session_id;
    console_redis_pub.set(session_sn, new_session_json, function (err, reply) {
        if (err) {
            console.log('写入session失败：\n' + new_session_json);
            return callback('写入session失败');
        }
        callback(null, new_session_id + ('0000000' + ENV.SOCKET_ROUTER_ID).substr(-8));
    });
}

var app = https.createServer(ENV.HTTPS, exec_request).listen(ENV.LISTEN_PORT);
var io = require('socket.io').listen(app);
io.sockets.on('connection', function (socket) {
    ENV._ONLINE_CLIENT_NUMBER++;
    console.log(uim.dateTime() + ' - 客户端连接：' + socket.request.connection.remoteAddress + '。在线总人数：' + ENV._ONLINE_CLIENT_NUMBER + '。socket.id：' + socket.id);
    //初始化socket
    socket.on('init', function (app_info, call_back) {
        // 校验app_info.deploy_app_id
        var verify_deploy_app_id = uim.verifySafeString(app_info.deploy_app_id, uim.config.enum.safeStringType.DEPLOY_APP_ID);
        if (!verify_deploy_app_id) {
            return call_back({ error: '校验失败' });
        }
        if (app_list[verify_deploy_app_id.id] === undefined) {
            return call_back({ error: '不接受该应用' });
        }
        if (!app_list[verify_deploy_app_id.id].enable) {
            return call_back({ error: '该应用暂不提供服务' });
        }

        var return_obj = {
            error: '',
            socket_server_list
        };

        var int_client_id, int_session_id;

        var check_init_id = function () {
            // 如果没有传入init_id（或为空）
            if (!app_info.init_id) {
                // 生成client_id
                get_new_client_id(socket.request.connection.remoteAddress, function (err, new_client_id) {
                    if (err) {
                        return call_back({ error: '初始客户端失败：' + err })
                    }
                    int_client_id = new_client_id;
                    console.log('新的client_id：' + int_client_id);
                    return_obj.init_id = uim.createSafeString({
                        int_unique: int_client_id,
                        int_tag: uim.config.enum.safeStringType.CLIENT_ID
                    });
                    check_session_id();
                });
            } else {
                // 校验app_info.init_id
                var verify_init_id = uim.verifySafeString(app_info.init_id, uim.config.enum.safeStringType.CLIENT_ID);
                if (!verify_init_id) {
                    return call_back({ error: '校验失败：init_id' });
                }
                // todo 取client_id信息，判断并记录init_id时间戳变早的情况（用户手动操作，或恢复系统镜像）
                int_client_id = verify_init_id.id;
                return_obj.init_id = uim.createSafeString({
                    int_unique: int_client_id,
                    int_tag: uim.config.enum.safeStringType.CLIENT_ID
                });
                check_session_id();
            }
        }
        var check_session_id = function () {
            if (app_info.session_id) {
                // 校验app_info.session_id
                var verify_session_id = uim.verifySafeString(app_info.session_id, uim.config.enum.safeStringType.SESSION_ID);
                if (!verify_session_id) {
                    return call_back({ error: '校验失败' });
                }
                // todo 判断会话是否属于本router，不属于则漫游取回
                let str_session_id = verify_session_id.id.toString();
                if (str_session_id.substr(-8) * 1 != ENV.SOCKET_ROUTER_ID) {
                    // todo 会话漫游
                    return call_back({ error: '会话漫游功能开发中' })
                } else {
                    // todo 查询会话信息是否存在
                    int_session_id = str_session_id.substr(0, str_session_id.length - 8);
                    let session_sn = ENV.SESSION_PREFIX + ENV.SOCKET_ROUTER_ID + '/' + int_client_id + '/' + int_session_id;
                    console_redis_pub.get(session_sn, function (err, result) {
                        if (err) {
                            console.log('读取session失败');
                            return call_back({ error: '读取session失败' });
                        }
                        let session_data = (uim.parseJSON(result) || {}).session_data;
                        if (session_data) {
                            return_obj.account_id = session_data.account_id;
                        }
                        if (return_obj.account_id) {
                            return_obj.account_id = uim.createSafeString({
                                int_unique: return_obj.account_id, // 生成新的account_id
                                int_tag: uim.config.enum.safeStringType.ACCOUNT_ID
                            });
                        }
                        return_obj.session_id = uim.createSafeString({
                            int_unique: verify_session_id.id, // 生成新的session_id
                            int_tag: uim.config.enum.safeStringType.SESSION_ID
                        });
                        select_socket_server();
                    });
                }
            } else {
                int_session_id = get_new_session_id(int_client_id, socket.request.connection.remoteAddress, function (err, new_session_id) {
                    if (err) {
                        return call_back({ error: '获取新会话失败：' + err })
                    }
                    int_session_id = new_session_id;
                    console.log('新的session_id：' + int_session_id);
                    return_obj.session_id = uim.createSafeString({
                        int_unique: int_session_id,
                        int_tag: uim.config.enum.safeStringType.SESSION_ID
                    });
                    select_socket_server();
                });
            }
        }
        var select_socket_server = function () {
            var new_socket_server_id = get_enable_socket_server();
            if (new_socket_server_id) {
                // 使用socket.id作为token
                var token = uim.createSafeString({
                    int_unique: new_socket_server_id * 1,
                    int_tag: uim.config.enum.safeStringType.SOCKET_ROUTER_TOKEN
                });
                return_obj.new_host = socket_server_list[new_socket_server_id].host;
                return_obj.token = token;
            } else {
                return_obj.error = '系统繁忙，工程师正在紧急处理，请稍后...';
            }
            return call_back(return_obj);
        }
        check_init_id(); // 开始执行
    });

    // //调试
    // socket.on('debug', run_debug);

    socket.on('disconnect', function (data) {
        ENV._ONLINE_CLIENT_NUMBER--;
        console.log(uim.dateTime() + ' - 客户端断开：' + socket.request.connection.remoteAddress + '。在线总人数：' + ENV._ONLINE_CLIENT_NUMBER + '。socket.id：' + socket.id);
    });

    // 触发客户端初始事件
    socket.emit('init');

});
var run_debug = function (debug_code) {
    console.log('debug: ' + debug_code);
    //此调试方法只在调试模式下使用
    if (ENV._DEBUG_MODE) eval(debug_code);
}

console.log('router server v2.0 is running: ' + uim.dateTime());
